use std::{future::IntoFuture, ops::Index};

use bytes::Bytes;
// use crate::{minio_client::MinioClientState, models::*};
use anyhow::{anyhow, Error, Result};
use axum::{
    Router,
    routing::{get, post, delete},
    extract::{Extension, Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
};
// use crate::http_helper::*;
use crate::{models, utils::{self, http_helper::*}};
use axum_macros::debug_handler;
use base64::prelude::*;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tracing::{debug, error};
use uuid::Uuid;

// use sqlx::PgPool;
// use tracing::debug;
use crate::app_error::*;
use crate::models::auth::*;
use crate::models::metainfo::*;
use crate::AppState;
use reqwest::{self, Url};
use std::sync::Arc;
use tower_http::trace::TraceLayer;

pub fn routes(state: Arc<AppState>) -> Router {
    Router::new()
        .route("/get_upload_url/{file_name}", get(get_upload_url))
        .route("/insert_meta_info", post(insert_meta_info))
        .route("/get_download_url/{file_path}",get(get_download_url))
        .route("/get_download_url2/{id}",get(get_download_url2))
        .route("/get_songs",get(get_songs))
        .route("/{id}/{file_path}",delete(delete_song))
        .route("/upload_by_url",post(upload_by_url))
        .route("/upload_by_url2",post(upload_by_url2))
        .route("/get_total_song_count",get(get_total_song_count))
        .route("/get_latest_songs",get(get_latest_songs))
        .route("/get_songs_by_title",get(get_songs_by_title))
        .route("/get_songs_by_artist",get(get_songs_by_artist))
        .route("/search_songs_by_title",get(search_songs_by_title))
        .layer(TraceLayer::new_for_http())
        .with_state(state)
}

fn generate_uuid_filename(title: &str) -> Result<String, Error> {
    debug!("title: {}", title);
    let index = title.rfind('.').ok_or(anyhow!("no file extension"))?;
    debug!("index: {}", index);

    let name = &title[..index];
    let subfix = &title[index..];

    debug!("name: {}, subfix: {}", name, subfix);

    let base64_file_name = BASE64_URL_SAFE_NO_PAD.encode(name.as_bytes());

    debug!("base64_file_name: {}", base64_file_name);

    Ok(format!(
        "{}_{}{}",
        uuid::Uuid::new_v4(),
        base64_file_name,
        subfix
    ))
}

// 请求上传文件
pub async fn get_upload_url(
    State(app_state): State<Arc<AppState>>,
    _: AdminClaims,
    Path(file_name): Path<String>,
) -> Result<impl IntoResponse, AppError> {
    let file_path = generate_uuid_filename(file_name.as_str())?;
    let url = app_state
        .minio_state
        .get_presigned_object_upload_url(file_path.as_str())
        .await?;

    let j = json!({
        "url": url,
        "file_path": file_path
    });
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: j,
    }))
}

// 定义一个异步函数insert_meta_info，用于插入歌曲元信息
pub async fn insert_meta_info(
    State(app_state): State<Arc<AppState>>,
    _: AdminClaims,
    AppJson(meta_info): AppJson<SongMetaInfo>,
) -> Result<impl IntoResponse, AppError> {
    tracing::info!("upload complete {}", meta_info.title);
    // 将歌曲元信息插入到数据库中
    app_state.pg_dao.songs_dao.insert_song(&meta_info).await?;

    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: "",
    }))
}

// walkaround
pub async fn upload_by_url(
    State(app_state): State<Arc<AppState>>,
    _: AdminClaims,
    AppJson(mut upload_req): AppJson<UploadSongRequest>,
) -> Result<impl IntoResponse, AppError> {
    let subfix = Url::parse(upload_req.url.as_str())?
        .path()
        .split(".")
        .last()
        .ok_or(anyhow!("invalid url without subfix"))?
        .to_string();

    let bytes = http_get(upload_req.url.as_str()).await?;
    let filename = format!("{}.{}", upload_req.song_meta_info.title, subfix);
    let file_path = generate_uuid_filename(filename.as_str())?;
    let minio_upload_url = app_state
        .minio_state
        .get_presigned_object_upload_url(file_path.as_str())
        .await?;

    upload_req.song_meta_info.file_path = file_path;

    // 写入pg
    app_state
        .pg_dao
        .songs_dao
        .insert_song(&upload_req.song_meta_info)
        .await?;

    // 上传到minio
    http_put(minio_upload_url.as_str(), bytes).await?;

    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: "",
    }))
}

// 这个是一个最直接的方法，但是有问题调用minio put_object方法时axum会报错，暂时不知道为什么
#[debug_handler]
pub async fn upload_by_url2(
    // 从请求中获取app_state
    State(app_state): State<Arc<AppState>>,
    // 从请求中获取管理员权限
    _: AdminClaims,
    // 从请求中获取上传歌曲请求
    AppJson(upload_req): AppJson<UploadSongRequest>,
) -> Result<impl IntoResponse, AppError> {
    // 通过url获取歌曲的二进制数据
    let bytes = http_get(upload_req.url.as_str()).await?;

    // 写入pg
    app_state
        .pg_dao
        .songs_dao
        .insert_song(&upload_req.song_meta_info)
        .await?;

    // 上传到minio
    app_state
        .minio_state
        .put_object(upload_req.song_meta_info.title.as_str(), &bytes).await?;

    // 返回成功响应
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: "",
    }))
}

// 请求下载文件
pub async fn get_download_url(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    Path(file_path): Path<String>,
) -> Result<impl IntoResponse, AppError> {
    let url = app_state
        .minio_state
        .get_presigned_object_download_url(file_path.as_str())
        .await?;

    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "url": url
        }),
    }))
}

pub async fn get_download_url2(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    Path(id): Path<i64>,
) -> Result<impl IntoResponse, AppError> {
    let song_meta_info = app_state.pg_dao.songs_dao.get_song(id).await?;

    app_state
        .minio_state
        .exists_object(song_meta_info.file_path.as_str())
        .await?;

    let url = app_state
        .minio_state
        .get_presigned_object_download_url(song_meta_info.file_path.as_str())
        .await?;

    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "url": url
        }),
    }))
}

#[derive(Deserialize)]
pub struct Pagination {
    page: i64,
    per_page: i64,
}

// "pagination": {
//     "page": 1,
//     "pageSize": 10,
//     "total": 100,
//     "totalPages": 10
//   }
// ?page=2&per_page=30
pub async fn get_songs(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    pagination: Query<Pagination>,
) -> Result<impl IntoResponse, AppError> {
    let songs: Vec<SongMetaInfo> = app_state
        .pg_dao
        .songs_dao
        .get_songs(pagination.page, pagination.per_page)
        .await?;
    let total = app_state.pg_dao.songs_dao.get_song_count().await?;
    let j = json!({
        "songs": songs,
        "pagination": {
            "page": pagination.page,
            "pageSize": pagination.per_page,
            "total": total,
        }
    });

    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: j,
    }))
}

pub async fn delete_song(
    State(app_state): State<Arc<AppState>>,
    _: AdminClaims,
    Path((id, file_path)): Path<(i64, String)>,
) -> Result<impl IntoResponse, AppError> {
    app_state.pg_dao.songs_dao.delete_song(id).await?;
    app_state
        .minio_state
        .delete_object(file_path.as_str())
        .await?;
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: "",
    }))
}

// 自定义 404 处理函数
pub async fn handle_404() -> impl IntoResponse {
    (
        StatusCode::NOT_FOUND,
        AppJson(
            json!({ "error": "Not Found", "message": "The requested API endpoint does not exist." }),
        ),
    )
}

pub async fn get_total_song_count(
    State(app_state): State<Arc<AppState>>,
    _: AdminClaims,
) -> Result<impl IntoResponse, AppError> {
    let count = app_state.pg_dao.songs_dao.get_song_count().await?;
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "total": count,
        }),
    }))
}

#[derive(Deserialize)]
pub struct LatestSongsReq {
    days: i64,
    limit: i64,
}

pub async fn get_latest_songs(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    req: Query<LatestSongsReq>,
) -> Result<impl IntoResponse, AppError> {
    let songs: Vec<SongMetaInfo> = app_state
        .pg_dao
        .songs_dao
        .get_latest_songs(req.limit, req.days)
        .await?;
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "songs": songs,
        }),
    }))
}

#[derive(Deserialize)]
pub struct SongTile {
    title: String,
}
pub async fn get_songs_by_title(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    Query(song_title): Query<SongTile>,
) -> Result<impl IntoResponse, AppError> {
    debug!("get_songs_by_title: {}", song_title.title);

    let songs: Vec<SongMetaInfo> = app_state
        .pg_dao
        .songs_dao
        .get_songs_by_title(&song_title.title)
        .await?;
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "songs": songs,
        }),
    }))
}

#[derive(Deserialize)]
pub struct SongArtist {
    artist: String,
}

pub async fn get_songs_by_artist(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    Query(artist): Query<SongArtist>,
) -> Result<impl IntoResponse, AppError> {
    let songs: Vec<SongMetaInfo> = app_state
        .pg_dao
        .songs_dao
        .get_songs_by_artist(&artist.artist)
        .await?;
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "songs": songs,
        }),
    }))
}

pub async fn search_songs_by_title(
    State(app_state): State<Arc<AppState>>,
    _: UserClaims,
    Query(song_title): Query<SongTile>,
) -> Result<impl IntoResponse, AppError> {
    let songs: Vec<SongMetaInfo> = app_state
        .pg_dao
        .songs_dao
        .search_songs_by_title(&song_title.title)
        .await?;
    Ok(AppJson(SuccessResponse {
        code: 200,
        message: "ok".to_string(),
        data: json!({
            "songs": songs,
        }),
    }))
}